﻿using System;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using log4net;
using Reactor.ServiceGrid.Workflows.Contexts;
using Samurai.Wakizashi.Workflow;
using TimeoutException = System.TimeoutException;

namespace Reactor.ServiceGrid.Workflows.Steps
{
    internal class StopExistingServiceIfNecessary : WorkflowStepBase<ServiceCommandContext>
    {
        private static readonly ILog Log = LogManager.GetLogger(typeof(StopExistingServiceIfNecessary));

        #region Overrides of WorkflowStepBase<AdoptionContext>

        public override void ExecuteStep()
        {
            var serviceName = Context.ServiceIdentifier.ToServiceInstanceString();
            var existingService = ServiceController.GetServices().SingleOrDefault(s => s.ServiceName == serviceName);
            Context.WindowsServiceName = serviceName;

            if (existingService != null)
            {
                Context.WindowsServiceController = existingService;
                existingService.Refresh();
                var serviceStatus = existingService.Status;
                if (Log.IsInfoEnabled) Log.InfoFormat("Found Windows Service named '{0}' with a status of: {1}", serviceName, serviceStatus);

                // No need to continue if the service isnt running.
                if (serviceStatus == ServiceControllerStatus.Stopped)
                {
                    NotifyStepCompleted();
                    return;
                }

                var stopWatch = new Stopwatch();
                var serviceResponseTimeout = TimeSpan.FromMinutes(2);

                try
                {
                    if (Log.IsDebugEnabled) Log.Debug("Attempting to stop existing Windows Service...");

                    existingService.Stop();
                    stopWatch.Start();
                    existingService.WaitForStatus(ServiceControllerStatus.Stopped, serviceResponseTimeout);
                    stopWatch.Stop();

                    if(stopWatch.Elapsed > serviceResponseTimeout)
                        throw new TimeoutException(string.Format("Timeout period of {0} expired.", stopWatch.Elapsed));

                    if(existingService.Status != ServiceControllerStatus.Stopped)
                        throw new InvalidOperationException(string.Format("Windows Service named '{0}' did not stop as instructed.", serviceName));
                }
                catch (InvalidOperationException ioe)
                {
                    if (Log.IsErrorEnabled)
                        Log.ErrorFormat("Unable to stop existing Windows Service named '{0}'. Reason: {1}", serviceName, ioe.Message);

                    NotifyStepFailed(ioe);
                    return;
                }
                catch(TimeoutException timeoutException)
                {
                    if(Log.IsErrorEnabled)
                        Log.Error(string.Format("Timed out waiting for service named {0} to stop. Cannot continue adoption workflow.", serviceName), timeoutException);

                    NotifyStepFailed(timeoutException);
                    return;
                }
            }
            else
            {
                if (Log.IsInfoEnabled) Log.InfoFormat("No existing Windows Service named '{0}'", serviceName);
            }

            NotifyStepCompleted();
        }

        #endregion
    }
}
